<?php

require_once 'Zend/Socket.php';

/**
 * @category   Zend
 * @package    Zend_Socket
 * @copyright  Salomon BRYS
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 *
 * Encapsulates the select call.
 *
 * It uses Zend_Socket_Holder_Interface instead of directly Zend_Socket so it can
 * manipulates any socket holder, including a basic Zend_Socket.
 */
class Zend_Socket_Select
{
	/**
	 * The select with Zend_Select (not the resources).
	 *
	 * @param array[int]Zend_Socket_Holder_Interface $reads
	 * @param array[int]Zend_Socket_Holder_Interface $writes
	 * @param array[int]Zend_Socket_Holder_Interface $excepts
	 * @param float $timeout
	 *
	 * @return int
	 */
	static public function Select(array &$reads, array &$writes, array &$excepts, $timeout = null)
	{
		$socketHolders = array();

		foreach (array_merge($reads, $writes, $excepts) as $holder) {
			if (!isset($socketHolders[(int)$holder->Socket()->getResource()])) {
				$socketHolders[(int)$holder->Socket()->getResource()] = $holder;
			}
		}

		return self::_doSelect($socketHolders, $reads, $writes, $excepts, $timeout);
	}

	/**
	 * The select with an array of (int)resource => Zend_Socket.
	 *
	 * @param array[(int)resource]Zend_Socket_Holder_Interface $sockets
	 * @param array[]Zend_Socket_Holder_Interface $reads
	 * @param array[]Zend_Socket_Holder_Interface $writes
	 * @param array[]Zend_Socket_Holder_Interface $excepts
	 * @param float $timeout
	 */
	static private function _doSelect(array $socketHolders, array &$reads, array &$writes, array &$excepts, $timeout = null)
	{
		$sRead = array();
		foreach ($reads as $holder) {
			$sRead[] = $holder->Socket()->getResource();
		}
		$reads = array();

		$sWrite = array();
		foreach ($writes as $holder) {
			$sWrite[] = $holder->Socket()->getResource();
		}
		$writes = array();

		$sExcept = array();
		foreach ($excepts as $holder) {
			$sException[] = $holder->Socket()->getResource();
		}
		$excepts = array();

		if (empty($sRead) && empty($sWrite) && empty($sExcept)) {
				require_once 'Zend/Socket/Exception.php';
				throw new Zend_Socket_Exception(
					"Empty select."
				);
		}

		$tv_sec = null;
		$tv_usec = null;
		if ($timeout !== null) {
			if ($timeout < 0) {
				$tv_sec = $tv_usec = 0;
			} else {
				$tv_sec = floor($timeout);
				$tv_usec = ($timeout - $tv_sec) * 1000000;
			}
		}

		$ret = socket_select($sRead, $sWrite, $sExcept, $tv_sec, $tv_usec);

		foreach ($sRead as $resource) {
			$reads[] = $socketHolders[(int)$resource];
		}

		foreach ($sWrite as $resource) {
			$writes[] = $socketHolders[(int)$resource];
		}

		foreach ($sExcept as $resource) {
			$excepts[] = $socketHolders[(int)$resource];
		}

		return $ret;
	}

	/**
	 * @var array[(int)resource]Zend_Socket_Holder_Interface
	 */
	public $_holders;

	/**
	 * Register a Socket for the select to be called with run().
	 *
	 * @param Zend_Socket_Holder_Interface $socket
	 * @return bool Wether it was registered (true) or already registered (false).
	 */
	public function register(Zend_Socket_Holder_Interface $holder)
	{
		if (isset($this->_holders[(int)$holder->Socket()->getResource()])) {
			return false;
		}
		$this->_holders[(int)$holder->Socket()->getResource()] = $holder;
		return true;
	}

	/**
	 * Unregister a Socket.
	 *
	 * @param Zend_Socket $socket
	 * @return bool Wether it was unregistered (true) or already unregistered (false).
	 */
	public function unRegister(Zend_Socket_Holder_Interface $holder)
	{
		if (isset($this->_holders[(int)$holder->Socket()->getResource()])) {
			unset($this->_holders[(int)$holder->Socket()->getResource()]);
			return true;
		}
		return false;
	}

	/**
	 * Run a select on registered sockets.
	 *
	 * @param float $timeout
	 * @param bool $withExcept
	 */
	public function run($timeout = null, $withExcept = false)
	{
		$ret = array('read' => array(), 'write' => array());
		if ($withExcept) {
			$ret['except'] = array();
			$except &= $ret['except'];
		} else {
			$except = array();
		}
		foreach ($this->_holders as $holder) {
			if (($holder->Socket()->getProtection() & Zend_Socket::PROT_READ) || ($holder->Socket()->getProtection() & Zend_Socket::PROT_LISTEN)) {
				$ret['read'][] = $holder->Socket();
			}
			if (($holder->Socket()->getProtection() & Zend_Socket::PROT_WRITE) && $holder->Socket()->writeLen() > 0) {
				$ret['write'][] = $holder->Socket();
			}
			if ($withExcept) {
				$ret['except'] = $holder->Socket();
			}
		}
		self::_doSelect($this->_holders, $ret['read'], $ret['write'], $except, $timeout);
		return $ret;
	}

	/**
	 * Get the registered holders
	 *
	 * @return array[(int)resource]Zend_Socket_Holder_Interface
	 */
	public function getRegistered()
	{
		return $this->_holders;
	}
}
